#include "glare.core.DrawContext.h"
#include "glare.core.ColourValue.h"

#include "Demo.Scene.h"
#include "glare.core.ShaderFactory.h"

#if GLARE_PLATFORM == GLARE_PLATFORM_WIN32
#	include "glare.d3d11.Driver.h"
#endif

#include "glare.gles2.Driver.h"
#include "glare.core.Shader.h"
#include "glare.core.ShaderInputDeclaration.h"
#include "glare.core.GpuBufferDescription.h"
#include "glare.core.GpuResourceFactory.h"

using namespace Glare;

class HelloIndexBuffer : public DemoScene
{
public : struct Vertex
		 {
			 Vector3 Pos;
			 Vector3 Rgb;
		 };

public : virtual void Initialize(Driver* driver, PlatformWindow* platform_window) 
		 {
			 _driver = driver;

			 try
			 {
				 _render_window = driver->CreateRenderWindow(platform_window);
				 _render_window->Initialize();
				 _render_window->SetPresentSyncInterval(1);

				 _immediate_context = _driver->GetImmediateDrawContext();

				 _viewport.Left = 0;
				 _viewport.Top = 0;
				 _viewport.Width = (float)_render_window->GetCanvasWidth();
				 _viewport.Height = (float)_render_window->GetCanvasHeight();
				 _viewport.MinDepth = 0;
				 _viewport.MaxDepth = 1;

				 // get the Shader / InputDeclaration factories
				 ShaderFactory* shader_factory = driver->GetShaderFactory();
				 _vertex_shader = shader_factory->CreateVertexShader();
				 _pixel_shader = shader_factory->CreatePixelShader();

				 string errors;
				 // load the vertex & pixel shader
#if GLARE_PLATFORM == GLARE_PLATFORM_WIN32
				 if (driver->GetName() == D3D11Driver::Name)
				 {
					 string shader_structs = 
						 "								\
						 struct Vertex					\
						 {								\
						 float4 Pos : POSITION0;		\
						 float3 Rgb : COLOR0;		\
						 };								\
						 struct PS_Input				\
						 {								\
						 float4 Pos : SV_Position;	\
						 float3 Rgb : COLOR0;		\
						 };";

					 string vertex_shader = 
						 shader_structs + 
						 "								\
						 PS_Input main(Vertex input)	\
						 {								\
						 PS_Input output;			\
						 output.Pos = input.Pos;		\
						 output.Rgb = input.Rgb;		\
						 return output;				\
						 }";

					 string pixel_shader = 
						 shader_structs + 
						 "											\
						 float4 main(PS_Input input) : SV_Target	\
						 {											\
						 return float4(input.Rgb, 1);			\
						 }";

					 _vertex_shader->Compile(vertex_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling vertex shader: " + errors, LMT_Error);

					 _pixel_shader->Compile(pixel_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling pixel shader: " + errors, LMT_Error);
				 }
#endif
				 if (driver->GetName() == GLES2Driver::Name)
				 {
					 string varying_vars = "varying lowp vec3 Rgb;";

					 string vertex_shader = 
						 varying_vars + 
						 "								\
						 attribute vec4 POSITION;		\
						 attribute vec3 COLOR;			\
						 void main(void)				\
						 {								\
						 Rgb = COLOR;				\
						 gl_Position = POSITION;		\
						 }";

					 string pixel_shader = 
						 varying_vars + 
						 "									\
						 void main(void)					\
						 {									\
						 gl_FragColor = vec4(Rgb, 1.0);	\
						 }";	

					 _vertex_shader->Compile(vertex_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling vertex shader: " + errors, LMT_Error);

					 _pixel_shader->Compile(pixel_shader, &errors);

					 if (errors.length() > 0)
						 driver->WriteLog("error while compiling pixel shader: " + errors, LMT_Error);
				 }

				 // create an input declaration for the vertex shader
				 _input_decl = shader_factory->CreateInputDeclaration();

				 // add the elements to the input declaration
				 ShaderInputElement position_elem;
				 position_elem.Format = PF_R32G32B32_FLOAT;
				 position_elem.SemanticName = "POSITION";
				 _input_decl->InputElements.push_back(position_elem);

				 ShaderInputElement color_elem;
				 color_elem.Format = PF_R32G32B32_FLOAT;
				 color_elem.SemanticName = "COLOR";
				 color_elem.Offset = sizeof(Vector3);
				 _input_decl->InputElements.push_back(color_elem);

				 // build the input declaration for the vertex shader
				 _input_decl->Build(_vertex_shader);

				 GpuResourceFactory* resource_factory = driver->GetGpuResourceFactory();

				 GpuBufferDescription desc;
				 desc.BindFlags = GpuResourceBind::VertexBuffer;
				 desc.ElementSize = sizeof(Vertex);
				 desc.ElementCount = 6;

				 // create the vertex buffer
				 _vertex_buffer = resource_factory->CreateBuffer(desc);

				 // create a binding for the vertex buffer
				 _vb_binding.Buffer = _vertex_buffer;
				 _vb_binding.Stride = sizeof(Vertex);
				 _vb_binding.Offset = 0;

				 Box dest_box;
				 dest_box.right = sizeof(Vertex) * 6;

				 Vertex vertices[6] = 
				 {
					 { Vector3(-1, -1, 0),		Vector3(1, 1, 0) },	// bottom left
					 { Vector3(0, -1, 0),		Vector3(1, 1, 0) },	// bottom center
					 { Vector3(1, -1, 0),		Vector3(1, 1, 0) },	// bottom right
					 { Vector3(0.5f, 0, 0),		Vector3(1, 0, 1) },	// middle right
					 { Vector3(0, 1, 0),		Vector3(0, 1, 1) },	// top center
					 { Vector3(-0.5f, 0, 0),	Vector3(1, 0, 1) },	// middle left
				 };

				 // copy the data to the vertex buffer
				 _immediate_context->UpdateGpuResource(_vertex_buffer, 0, &dest_box, vertices, sizeof(Vertex), 0);

				 desc.BindFlags = GpuResourceBind::IndexBuffer;
				 desc.ElementSize = sizeof(Glare::uint);
				 desc.ElementCount = 9;

				 _index_buffer = resource_factory->CreateBuffer(desc);

				 dest_box.right = sizeof(Glare::uint) * 9;

				 Glare::uint indices[9] =
				 {
					 0, 5, 1,
					 1, 3, 2,
					 5, 4, 3
				 };

				 _immediate_context->UpdateGpuResource(_index_buffer, 0, &dest_box, indices, sizeof(Glare::uint), 0);
			 }
			 catch (Exception e)
			 {
				 _driver->WriteLog(e.what(), LMT_Error);
			 }
		 }

public : virtual void DrawFrame() 
		 {
			 // set the RenderTarget, the Viewport and perform a clearing operation
			 _immediate_context->OM_SetRenderTarget(_render_window, NULL);
			 _immediate_context->RS_SetViewport(_viewport);
			 _immediate_context->ClearRenderTarget(_render_window, ColourValue::Red);

			 // activate the vertex buffer
			 _immediate_context->IA_SetVertexBuffer(_vb_binding);

			 // activate the index buffer
			 _immediate_context->IA_SetIndexBuffer(_index_buffer, true, 0);

			 // set the vertex & pixel shader
			 _immediate_context->VS_SetVertexShader(_vertex_shader);
			 _immediate_context->PS_SetPixelShader(_pixel_shader);

			 // set the primitive type & input declaration for the draw call
			 _immediate_context->IA_SetPrimitiveType(GpuPrimitive::TriangleList);
			 _immediate_context->IA_SetInputDeclaration(_input_decl);

			 // submit the draw call
			 _immediate_context->DrawIndexed(9, 0, 0);

			 // swap the GPU buffers
			 _render_window->Present();
		 }

public : virtual void Destroy() 
		 {
			 delete _index_buffer;
			 _index_buffer = NULL;

			 delete _vertex_buffer;
			 _vertex_buffer = NULL;

			 delete _input_decl;
			 _input_decl = NULL;

			 delete _pixel_shader;
			 _pixel_shader = NULL;

			 delete _vertex_shader;
			 _vertex_shader = NULL;

			 _driver->DestroyRenderWindow(_render_window);
			 _render_window = NULL;
		 }

private : Driver* _driver;
private : RenderWindow* _render_window;
private : DrawContext* _immediate_context;
private : Viewport _viewport;

private : VertexShader* _vertex_shader;
private : PixelShader* _pixel_shader;
private : ShaderInputDeclaration* _input_decl;
private : GpuBuffer* _vertex_buffer;
private : GpuVertexBufferBinding _vb_binding;
private : GpuBuffer* _index_buffer;
};
